package com.dasberg.gwt.aop;

import com.dasberg.guice.StaticInjector;
import com.dasberg.gwt.client.AuthenticationException;
import com.dasberg.gwt.client.AuthenticationToken;
import com.dasberg.gwt.client.InvalidAuthenticationTokenException;
import com.dasberg.gwt.client.MissingAuthenticationTokenException;
import com.dasberg.gwt.server.AuthenticationProvider;
import com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Aspect for checking authentication.
 * Each RemoteService implementation that has the annotation @RequiresAuthorization on a method will be intercepted and
 * checked to see if the user is still authenticated with the system.
 * @author mischa
 */
@Aspect
public class AuthenticationChecker {
    @Inject
    private AuthenticationProvider provider;

    @Pointcut(" execution(@com.dasberg.gwt.aop.RequiresAuthorization public * *(..)) && target(instantiated)")
    public void methodToBeCheckedForAuthentication(Object instantiated) {
    }


    @After(" methodToBeCheckedForAuthentication(instantiated) ")
    public void onInstantiation(Object instantiated) throws AuthenticationException {
        if (instantiated instanceof RemoteServiceServlet) {
            AuthenticationToken o = getAuthenticationToken((RemoteServiceServlet) instantiated);

            if(provider == null) {
                StaticInjector.injectDependencies(this);
            }
            if (o == null) {
                throw new MissingAuthenticationTokenException();
            } else if (!provider.isTokenValid(o)) {
                throw new InvalidAuthenticationTokenException();
            }
        }
    }

    /**
     * Get the session from the RemoteServiceServlet.
     * @param servlet The servlet.
     * @return session The HttpSession.
     */
    public AuthenticationToken getAuthenticationToken(RemoteServiceServlet servlet) {
        try {
            Method method = AbstractRemoteServiceServlet.class.getDeclaredMethod("getThreadLocalRequest");
            method.setAccessible(true);
            return (AuthenticationToken) ((HttpServletRequest) method.invoke(servlet)).getSession().getAttribute("authenticationToken");
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }
}